iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
1
Modern Web

站在Web前端人員角度,學習 Spring Boot 後端開發系列 第 24

Day 24 - Spring Boot ReactJS x ToDoList API 串起來

  • 分享至 

  • xImage
  •  

昨天我們已經快速建立TodoList的專案,並簡單的創造一個模版與切割好各個components。今天要來串接我們之前寫好的RESTfulAPI了。

[GET] /api/todos
[POST] /api/todos
[PUT] /api/todos/{id}
[DELETE] /api/todos/{id}

Components 元件圖

https://ithelp.ithome.com.tw/upload/images/20201003/20118857BTChM4l9LO.png

(1)首先,因為api網址為http://localhost:9100/,TodoList 網址為http://localhost:3000/互相溝通會有跨網域的問題,所以我們利用webpack提供的proxy將api的網址代理掉。

package.json

"proxy":"http://localhost:9100/api"

(2)準備todo list api 的CRUD吧,在src / service/ todos.js 與 src / service/ helper.js

src / service/ helper.js 實作一些API回應處理

export const handleResponse = (response) => {
    return response.text().then((text) => {
        const data = text && JSON.parse(text);
        if (!response.ok) {
            const errorLog = {
                status: response.status,
                code: (data && data.ErrorCode) || '',
                msg: (data && data.ErrorMessage) || response.statusText,
            };
            return Promise.reject(errorLog);
        }
        return data;
    });
};

撰寫CRUD的function

import { handleResponse } from './helper';

const getTodos =()=> {
    const requestOptions = {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' },
    };

    return fetch(`/todos`, requestOptions)
        .then(handleResponse)
        .then((todos) => {
            return todos;
        });
};

const createTodos =(data)=> {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    };

    return fetch(`/todos`, requestOptions)
        .then(handleResponse)
        .then((res) => {
            return res;
        });
};

const updateTodos =(id, data)=> {
    const requestOptions = {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    };

    return fetch(`/todos/${id}`, requestOptions)
        .then(handleResponse)
        .then((res) => {
            return res;
        });
};

const deleteTodos =(id)=> {
    const requestOptions = {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json' }
    };

    return fetch(`/todos/${id}`, requestOptions)
        .then(handleResponse)
        .then((res) => {
            return res;
        });
};

export const todosService = {
    getTodos,
    createTodos,
    updateTodos,
    deleteTodos
};

(3)ToDo這個最外層的父組件,包含了3個子組件,分別是TitleBox, TodoForm與TodoItems,所以會在ToDo這一個組件放置要操作的todolist資料,並藉由props將資料及方法傳遞給子組件接收。

import React from 'react';
import TitleBox from '../components/TitleBox';
import TodoForm from '../components/TodoForm';
import TodoItems from '../components/TodoItems';
import { todosService } from '../service//todo.js';
import { useState, useEffect } from 'react';

const ToDoList = () => {
    const [todos, setTodos] = useState([]);

    useEffect(() => {
        // 頁面載入時,取得代辦事項列表
        todosService.getTodos().then((data) => {
            setTodos(data);
        });
    }, []);

    const handleAdd = (text) => {
	        // 接收從TodoForm 呼叫的handleAdd()方法
        const data = {
            task: text,
        };
        todosService.createTodos(data).then((res) => {
            data.id = res;
            data.status = res;
            todos.push(data);
            setTodos([...todos]);
        });
    };

    const handleUpdate = (id, data) => {
        // 接收從TodoItems 呼叫的handleUpdate()方法
        todosService.updateTodos(id, data).then((res) => {
            const mapTodos = todos.map((todo) => {
                if (todo.id === id) {
                    todo.status = data.status;
                }
                return todo;
            });
            setTodos(mapTodos);
        });
    };

    const handleDelete = (id) => {
				// 接收從TodoItems 呼叫的handleDelete()方法
        todosService.deleteTodos(id).then((res) => {
            todos.forEach((todo, index) => {
                if (todo.id === id) {
                    todos.splice(index, 1);
                }
            });
            setTodos([...todos]);
        });
    };

    return (
        <div className="container">
            <TitleBox />
            <div className="todo-box">
                <TodoForm handleAdd={handleAdd} />
                <TodoItems todos={todos} handleUpdate={handleUpdate} handleDelete={handleDelete} />
            </div>
        </div>
    );
};

export default ToDoList;

(4)接著實作TodoItems的列表顯示,在頁面載入時要呼叫[GET]/todos API取得列表,todos資料並透過props傳遞給子組件(TodoItems),子組件接收到,利用map方法將資料渲染出來。

import React from 'react';

const TodoItems = ({ todos, handleUpdate, handleDelete }) => {
    const done = 2;
    const notDone = 1;
    return (
        <ul>
            {todos.map((todo, index) => (
                <li
                    className={todo.status === done ? 'checked' : ''}
                    key={index}
                    onClick={() => {
                        const data = {status: done};
                        if (todo.status === done) {
                            data.status = notDone;
                        }
                        handleUpdate(todo.id, data);
                    }}>
                    {todo.task} <span className="badge bg-red">生活</span>
                    <span className="close" onClick={(event)=> {
                        event.stopPropagation();
                        handleDelete(todo.id)
                        }}>X</span>
                </li>
            ))}
        </ul>
    );
};

export default TodoItems;

(5)新增代辦事項,由父組件(ToDo)傳遞一個handleAdd 方法讓TodoFrom 點擊新增時,可以呼叫此方法並call [POST] /todos ,更改代辦事項資料集。

import React from 'react';
import { useState } from 'react';

const TodoForm = ({handleAdd}) => {
    const [toDoText, setToDoText] = useState('');

    const onInputChange = (event) => {
        const value = event.target.value;
        setToDoText(value);
    }
    return (
    <div className="header">
        <input type="text" id="todoInput" name="todoInput" value={toDoText} placeholder="New Item..." onChange={onInputChange}/>
        <button type="submit" className="addBtn" onClick={()=> {handleAdd(toDoText)}}>
            Add
        </button>
    </div>
    )
}

export default TodoForm;

(5)更新代辦事項狀態,在TodoItems 接收從父組件傳來的handleUpdate()方法,點擊項目執行此方法,觸發到父組件呼叫[PUT] /todos/{id},待成功後整理資料並渲染在畫面上。

const TodoItems = ({ todos, handleUpdate, handleDelete }) => {
    const done = 2;
    const notDone = 1;
    return (
        <ul>
            {todos.map((todo, index) => (
                <li
                    className={todo.status === done ? 'checked' : ''}
                    key={index}
                    onClick={() => {
                        const data = {status: done};
                        if (todo.status === done) {
                            data.status = notDone;
                        }
			                  // 呼叫由props傳遞過來的handleUpdate()
                        handleUpdate(todo.id, data);
                    }}>
                    {todo.task} <span className="badge bg-red">生活</span>
                </li>
            ))}
        </ul>
    );
};

(5)刪除代辦事項,在TodoItems 接收從父組件傳來的handleDelete()方法,點擊「X」執行此方法,觸發到父組件呼叫[DELETE] /todos/{id},待成功後整理資料並渲染在畫面上。

<span
      className="close"
      onClick={(event) => {
          event.stopPropagation();
          handleDelete(todo.id);
      }}>
      X
</span>


上一篇
Day 23 - Spring Boot 快速建立前端ToDoList專案-ReactJS
下一篇
Day 25 - Spring Boot logging 記錄記起來
系列文
站在Web前端人員角度,學習 Spring Boot 後端開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言